﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace RiskOdds
{
  public class RiskOdds
  {
    enum UnitRole {
      Attacker,
      Defenfer
    }

    Random r = new Random();

    public void RunSimulation(String path, int maxAttackerCount, int maxDefenderCount, int simCount)
    {
      StreamWriter s = new StreamWriter(path);
      for (int attackerCount = 1; attackerCount < maxAttackerCount + 1; attackerCount++)
      {
        for (int defenderCount = 1; defenderCount < maxDefenderCount + 1; defenderCount++)
        {
          double[][] simResults = SurvivingProbabilities(attackerCount, defenderCount, simCount);
          Console.WriteLine("Simulated: " + attackerCount + " " + defenderCount);
          String line = "";
          line += attackerCount.ToString().PadRight(4, ' ');
          line += defenderCount.ToString().PadRight(4, ' ');
          line += "|";
          // attacker probs
          line += SurvivingProbabilitiesToString(simResults[0]);
          s.WriteLine(line);
          // defender probs
          line = "|".PadLeft(9, ' ') + SurvivingProbabilitiesToString(simResults[1]);
          s.WriteLine(line);
          s.WriteLine("".PadLeft(9 + Math.Max(maxAttackerCount + 1, maxDefenderCount + 1) * 8, '-'));
        }
      }
      s.Close();
    }

    String SurvivingProbabilitiesToString(double[] percentages)
    {
      String result = "";
      for (int i = 0; i < percentages.Length; i++)
      {
        result += (Math.Round(percentages[i]).ToString() + "%" + "(" + i + ")").PadLeft(8, ' ');
      }
      return result;
    }


    String SurvivingProbabilitiesToString(int attackerUnitCounts, int defenderUnitCounts, int simCount)
    {
      double[] d = SurvivingProbabilities(attackerUnitCounts, defenderUnitCounts, simCount)[0];
      String result = "";
      for (int i = 0; i < d.Length; i++)
      {
        result += (Math.Round(d[i]).ToString() + "%" +"(" + i + ")").PadLeft(8, ' ');
      }
      return result;
    }

    /// <summary>
    /// Calculates the probablities of attacker's and defender's remaining armies after completed attacks.
    /// Returns the probabilities in an array: 
    /// double[0][number of attacking units remaining] = the percentage of battles
    /// double[1][number of defending units remaining] = the percentage of battles
    /// <param name="attackerUnitCounts">Initial number of attacking units</param>
    /// <param name="defenderUnitCounts">Initial number of defensing units</param>
    /// <param name="simCount">
    /// The number of total battles that will be simulated. A battle ends when either one of the 
    /// units counts reaches zero.
    /// </param>
    /// </summary>
    double[][] SurvivingProbabilities(int attackerUnitCounts, int defenderUnitCounts, int simCount)
    {
      double[][] result = new double[2][];
      result[0] = new double[attackerUnitCounts + 1];
      result[1] = new double[defenderUnitCounts + 1];
      int[] attackerUnitsLeftCounts = new int[attackerUnitCounts + 1];
      int[] defenderUnitsLeftCounts = new int[defenderUnitCounts + 1];
      for (int i = 0; i < simCount; i++)
      {
        int[] unitCounts = new int[] { attackerUnitCounts, defenderUnitCounts };
        while (unitCounts[0] > 0 && unitCounts[1] > 0)
        {
          DoBattle(unitCounts);
        }
        attackerUnitsLeftCounts[unitCounts[0]]++;
        defenderUnitsLeftCounts[unitCounts[1]]++;
      }
      for (int i = 0; i < attackerUnitsLeftCounts.Length; i++)
      {
        result[0][i] = (attackerUnitsLeftCounts[i] / (double)simCount * 100);
      }
      for (int i = 0; i < defenderUnitsLeftCounts.Length; i++)
      {
        result[1][i] = (defenderUnitsLeftCounts[i] / (double)simCount * 100);
      }
      return result;
    }

    int[] SimulateAndSortDices(int diceCount)
    {
      int[] result = new int[diceCount];
      for (int i = 0; i < diceCount; i++)
      {
        result[i] = r.Next(1, 6);
      }
      Array.Sort(result);
      Array.Reverse(result);
      return result;
    }

    /// <summary>
    /// Reduces unitCounts by simulating one battle.
    /// <param name="unitCounts">
    /// unitCounts[0] = initial number of attacking units
    /// unitCounts[1] = initial number of defending units
    /// </param>
    /// </summary>
    void DoBattle(int[] unitCounts)
    {
      if (unitCounts[0] < 1 || unitCounts[1] < 1) 
        throw new ArgumentOutOfRangeException();
      int[] aDiceResults = SimulateAndSortDices(Math.Min(unitCounts[0], 3));
      int[] dDiceResults = SimulateAndSortDices(Math.Min(unitCounts[1], 2));
      int maxLostUnitCount = Math.Min(aDiceResults.Length, dDiceResults.Length);
      for (int i = 0; i < maxLostUnitCount; i++)
      {
        if (dDiceResults[i] >= aDiceResults[i])
          unitCounts[0]--; // attacker loses one
        else
          unitCounts[1]--; // defender loses one
      }
    }

    static void Main(string[] args)
    {
      new RiskOdds().RunSimulation("sim100k.txt", 15, 15, 100000);
    }
  }
}
